/*
    Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements.  See the NOTICE file
    distributed with this work for additional information
    regarding copyright ownership.  The ASF licenses this file
    to you under the Apache License, Version 2.0 (the
    "License"); you may not use this file except in compliance
    with the License.  You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing,
    software distributed under the License is distributed on an
    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    KIND, either express or implied.  See the License for the
    specific language governing permissions and limitations
    under the License.
 */
package erwiki.api.auth;

import java.security.Principal;
import java.util.Collection;
import java.util.Map;

import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.spi.LoginModule;
import javax.servlet.http.HttpServletRequest;

import org.eclipse.jdt.annotation.NonNull;
import org.elwiki.data.authorize.GroupPrincipal;

import erwiki.api.component.IPartPreferences;
import erwiki.api.core.WikiSession;
import erwiki.api.event.LoginEvent;
import erwiki.api.exceptions.WikiException;
import erwiki.api.exceptions.WikiSecurityException;

/**
 * Управляет действиями по аутентификации: вход пользователя в систему, выход из системы и
 * обновление учетных данных. Этот класс использует JAAS для определения способа входа пользователей
 * в систему.
 * <p>
 * Процедура входа в систему дополнительно защищена механизмом, который не позволяет хакеру
 * попытаться принудительно угадывать пароли, замедляя попытки входа в ту же учетную запись. Каждая
 * попытка входа в систему записывается и сохраняется некоторое время (в настоящее время десять
 * минут), и в течение этого времени для каждой дальнейшей попытки входа в систему - взимается штраф
 * в размере 2^число предпринятых попыток входа в систему (миллисекунд), то есть 10я попытка входа в
 * систему будет иметь штраф в размере 1,024 секунды. В настоящее время задержка ограничена 20
 * секундами.
 */
public interface AuthenticationManager extends IPartPreferences {

	interface Prefs {
		/// Preferences names of Authentication Manager.
		/** Разрешается ли для идентификации использовать файлы cookie. По умолчанию <code>true</code>. */
		String ALLOW_COOKIE_ASSERTIONS = "cookieAssertions";

		/** Разрешается ли для аутентификации использовать файлы cookie. По умолчанию <code>true</code>. */
		String ALLOW_COOKIE_AUTH = "cookieAuthentication";

		/**
		 * Следует ли ограничивать процедуру регистрации, чтобы ограничить попытки грубого подбора пароля.
		 * По умолчанию <code>true</code>.
		 */
		String LOGIN_THROTTLING = "login.throttling";

		/** Определяет модуль {@link LoginModule}, используемый для аутентификации пользователя. */
		String LOGIN_MODULE_ID = "loginModule.id";
	}

	/** Prefix for LoginModule options key/value pairs. */
	@Deprecated // :FVK: - не удалять, а дописать конфигурирование опций Login модуля. см. initLoginModuleOptions()
	String PREFIX_LOGIN_MODULE_OPTIONS = "jspwiki.loginModule.options.";

	/**
	 * Ищет класс LoginModule через точку расширения "org.elwiki.authorize.loginModule".
	 *
	 * @param loginModuleId идентификатор модуля регистрации пользователя.
	 * @return экземпляр, реализующий LoginModule.
	 * @throws WikiException если что-то пойдет не так.
	 */
	Class<? extends LoginModule> getLoginModule(String loginModuleId) throws WikiException;

	/**
	 * Регистрирует пользователя, пытаясь заполнить Session Subject из запроса веб-сервлета, проверяя
	 * запрос на наличие учетных данных контейнера и файлов cookie пользователя.<br/>
	 * Логика обработки следующая:
	 * <ul>
	 * <li>Если Session ранее не был аутентифицирован, то проверяется, прошел ли пользователь
	 * впоследствии аутентификацию. Для «authenticated», запрос должен предоставить одно из следующих
	 * значений (в порядке предпочтения): контейнер <code>userPrincipal</code>, контейнер
	 * <code>remoteUser</code> или файл cookie аутентификации. Если пользователь аутентифицирован, то
	 * этот метод запускает событие {@link LoginEvent#AUTHENTICATED} с двумя параметрами: Principal,
	 * представляющий участника входа в систему, и текущую сессию. Кроме того, если авторизатор имеет
	 * тип WebContainerAuthorizer, то этот метод выполняет итерацию по роли контейнера, возвращаемые
	 * {@link WebContainerAuthorizer#getRoles()}, проверяет членство в каждой роли, и добавляет те,
	 * которые передаются в основной набор Subject.</li>
	 * <li>Если после проверки аутентификации Session по-прежнему остается анонимным, то этот метод
	 * затем проверяет, опознан ли пользователь как «asserted», предоставив файл cookie подтверждения.
	 * Если обнаружено, что пользователь подтвержден, этот метод запускает событие
	 * {@link LoginEvent#ASSERTED} с двумя параметрами:
	 * <code>WikiPrincipal(<em>cookievalue</em>)</code>, и текущую сессию.</li>
	 * <li>Если Session после проверки статуса аутентификации и подтверждения <em>все еще</em> анонимен,
	 * то этот метод генерирует событие {@link LoginEvent#ANONYMOUS} с двумя параметрами:
	 * <code>WikiPrincipal(<em>remoteAddress</em>)</code>, и текущая сессия</li>
	 * </ul>
	 *
	 * @param request запрос сервлета для этого пользователя.
	 * @param session WikiSession, которая должна быть аутентифицирована.
	 * @return <code>true</code> в случае успешного входа в систему;<br/>
	 *         <code>false</code> если по какой-то необычной причине, не сработал даже модуль анонимного
	 *         входа.
	 * @throws WikiSecurityException если пользователь по какой-либо причине не может войти в систему.
	 */
	boolean login(HttpServletRequest request, WikiSession session) throws WikiSecurityException;

	/**
	 * Пытается выполнить вход в Session для заданных username/password, используя специальный режим
	 * аутентификации wiki.<br/>
	 * Чтобы войти в систему, будет создан экземпляр JAAS LoginModule, предоставленный
	 * preference-свойством {@link #PROP_LOGIN_MODULE}, и будет вызван его метод
	 * {@link javax.security.auth.spi.LoginModule#initialize(Subject, CallbackHandler, Map, Map)}. По
	 * умолчанию, будет использоваться класс {@link AccountRegistryLoginModule}.<br/>
	 * При вызове метода <code>LoginModule#initialize</code>, в качестве аргумента будет передана карта
	 * параметров, заполненная ключами свойств с префиксом {@link #PREFIX_LOGIN_MODULE_OPTIONS}.
	 *
	 * @param session  текущая вики-сессия; не может быть <code>null</code>.
	 * @param request  HTTP-запрос пользователя. Этот параметр может иметь значение <code>null</code>,
	 *                 но в этом случае LoginModule не будет иметь доступа к HTTP-запросу.
	 * @param username Имя пользователя. Это имя для входа, а не WikiName. В большинстве случаев они
	 *                 одинаковы, но в некоторых случаях это может быть и не так.
	 * @param password пароль.
	 * @return <code>true</code>, если имя username/password действительны.
	 * @throws WikiSecurityException если Authorizer или AccountManager не могут быть получены.
	 */
	boolean loginAsserted(@NonNull WikiSession session, HttpServletRequest request, String username, String password)
			throws WikiSecurityException;

	/**
	 * Завершает работу пользователя, получая сеанс, связанный с HttpServletRequest, и отвязывая все
	 * принципалы субъекта, кроме {@link Role#ALL}, {@link Role#ANONYMOUS}. Это дешевый и удобный способ
	 * дизрегистрировать пользователя без вызова JAAS LoginModules.<br/>
	 * Операция выхода из системы также удалит файл cookie JSESSIONID из сеанса браузера пользователя,
	 * если он был установлен.
	 *
	 * @param request the current HTTP request
	 */
	void logout(HttpServletRequest request);

	/**
	 * Определяет, позволяется ли идентификация пользователей использовать файлы cookie вместо паролей.
	 * Это определяется проверкой свойства {@link #ALLOW_COOKIE_ASSERTIONS}.
	 *
	 * @return <code>true</code> если файлы cookie разрешены для идентификации.
	 */
	boolean isAllowsCookieAssertions();

	/**
	 * Определяет, позволяется ли аутентификация пользователей использовать файлы cookie вместо паролей.
	 * Это определяется проверкой свойства {@link #ALLOW_COOKIE_AUTH}.
	 *
	 * @return <code>true</code> если файлы cookie разрешены для аутентификации.
	 */
	boolean isAllowsCookieAuthentication();

	/**
	 * Возвращает true, если используется аутентификация, управляемая контейнером; в противном случае
	 * возвращается <code>false</code>.
	 *
	 * @return <code>true</code> если аутентификация вики-сайта управляется контейнером,
	 *         <code>false</code> в противном случае.
	 */
	boolean isContainerAuthenticated();

	/**
	 * Определяет, является ли указанный Principal - "role principal".
	 *
	 * @param principal проверяемый принципал.
	 * @return <code>true</code>, если Principal имеет тип {@link GroupPrincipal}, <code>false</code> в
	 *         противном случае.
	 */
	static boolean isRolePrincipal(final Principal principal) {
		return principal instanceof GroupPrincipal;
	}

	/**
	 * Определяет, является ли указанный Principal - "user principal".
	 *
	 * @param principal проверяемый принципал.
	 * @return <code>true</code>, если Principal <b>НЕ</b> имеет тип {@link GroupPrincipal},
	 *         <code>false</code> в противном случае.
	 */
	static boolean isUserPrincipal(final Principal principal) {
		return !isRolePrincipal(principal);
	}

	/**
	 * Возвращает первый Principal из набора, который не является {@link Role} или
	 * {@link GroupPrincipal}.
	 *
	 * @param principals набор принципалов.
	 * @return принципал входа в систему.
	 */
	static Principal getLoginPrincipal(Collection<Principal> principals) {
		for (final Principal principal : principals) {
			if (isUserPrincipal(principal)) {
				return principal;
			}
		}
		return null;
	}

}
